
/*
 * dd.c -- convert and copy files.  phr 22-march-85.
 */

#include <ctype.h>
#include <stdio.h>

#define equal(p, q) (strcmp ((p),(q)) == 0)
#define max(a, b) ((a) > (b) ? (a) : (b))

#define BLOCKSIZE 512		/* Unix dd uses this so we do too */

#define C_ASCII 01
#define C_EBCDIC 02
#define C_IBM 04
#define C_BLOCK 010
#define C_UNBLOCK 020
#define C_LCASE 040
#define C_UCASE 0100
#define C_SWAB 0200
#define C_NOERROR 0400
#define C_SYNC 01000
#define C_DEBUG 02000

#define C_HARDWAY 04000		/* some conversion is needed during i/o */

struct info
{
  char *i_if;			/* input filename */
  int i_ifd;			/* input file descriptor */
  char *i_of;			/* output filename */
  int i_ofd;			/* output file descriptor */
  int i_ibs;			/* input blocksize */
  int i_obs;			/* output blocksize */
  int i_cbs;			/* conversion buffer size */
  int i_skip;			/* skip this many records on input */
  int i_files;			/* skip this many files on input (TODO) */
  int i_seek;			/* seek to this record on output */
  int i_count;			/* copy only this many records */
  int i_convert;		/* bit vector of conversions to apply */
} info = { NULL, 0, NULL, 1, BLOCKSIZE, BLOCKSIZE, BLOCKSIZE, 0, 0, 0, -1, 0};

int w_partial = 0;		/* number of partial blocks written */
int w_full = 0;			/* numer of full blocks written */
int r_partial = 0;		/* number of partial blocks read */
int r_full = 0;			/* number of full blocks read */
int r_truncate = 0;		/* records truncated by conv=block */

struct conversion
{
  char *convname;
  int conversion;
} conversions[] = {
  "ascii",      C_ASCII | C_HARDWAY,	     /* ebcdic to ascii */
  "ebcdic",	C_EBCDIC | C_HARDWAY,	     /* ascii to ebcdic */
  "ibm",	C_IBM | C_HARDWAY,	     /* slightly different atoe */
  "block",	C_BLOCK | C_HARDWAY,	     /* var to fixed len recs */
  "unblock",	C_UNBLOCK | C_HARDWAY,	     /* fixed to var */
  "lcase",	C_LCASE | C_HARDWAY,	     /* translate upper to lower */
  "ucase",	C_UCASE | C_HARDWAY,	     /* translate lower to upper */
  "swab",	C_SWAB | C_HARDWAY,	     /* swap bytes of input */
  "noerror",	C_NOERROR,		     /* ignore i/o errors */
  "sync",	C_SYNC,			     /* pad input recs to ibs */
  "debug",      C_DEBUG,		     /* debugging output flag */
  NULL,		0,
};

char trans_table[256];          /* translation table formed by applying */
				/* successive transformations */

char ascii_to_ebcdic[] = {
  0,    01,   02,   03,   067,  055,  056,  057,  
  026,  05,   045,  013,  014,  015,  016,  017,  
  020,  021,  022,  023,  074,  075,  062,  046,  
  030,  031,  077,  047,  034,  035,  036,  037,  
  0100, 0117, 0177, 0173, 0133, 0154, 0120, 0175, 
  0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, 
  0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, 
  0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, 
  0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, 
  0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, 
  0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, 
  0347, 0350, 0351, 0112, 0340, 0132, 0137, 0155, 
  0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, 
  0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, 
  0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, 
  0247, 0250, 0251, 0300, 0152, 0320, 0241, 07,   
  040,  041,  042,  043,  044,  025,  06,   027,  
  050,  051,  052,  053,  054,  011,  012,  033,  
  060,  061,  032,  063,  064,  065,  066,  010,  
  070,  071,  072,  073,  04,   024,  076,  0341, 
  0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, 
  0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, 
  0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, 
  0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, 
  0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, 
  0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, 
  0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, 
  0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, 
  0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, 
  0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, 
  0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, 
  0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, 
};

char ascii_to_ibm[] = {
  0,    01,   02,   03,   067,  055,  056,  057,  
  026,  05,   045,  013,  014,  015,  016,  017,  
  020,  021,  022,  023,  074,  075,  062,  046,  
  030,  031,  077,  047,  034,  035,  036,  037,  
  0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175, 
  0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141, 
  0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, 
  0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157, 
  0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307, 
  0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326, 
  0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346, 
  0347, 0350, 0351, 0255, 0340, 0275, 0137, 0155, 
  0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207, 
  0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226, 
  0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246, 
  0247, 0250, 0251, 0300, 0117, 0320, 0241, 07,   
  040,  041,  042,  043,  044,  025,  06,   027,  
  050,  051,  052,  053,  054,  011,  012,  033,  
  060,  061,  032,  063,  064,  065,  066,  010,  
  070,  071,  072,  073,  04,   024,  076,  0341, 
  0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110, 
  0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127, 
  0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147, 
  0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165, 
  0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215, 
  0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236, 
  0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257, 
  0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, 
  0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, 
  0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333, 
  0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355, 
  0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377, 
};

char ebcdic_to_ascii[] = {
  0,    01,   02,   03,   0234, 011,  0206, 0177, 
  0227, 0215, 0216, 013,  014,  015,  016,  017,  
  020,  021,  022,  023,  0235, 0205, 010,  0207, 
  030,  031,  0222, 0217, 034,  035,  036,  037,  
  0200, 0201, 0202, 0203, 0204, 012,  027,  033,  
  0210, 0211, 0212, 0213, 0214, 05,   06,   07,   
  0220, 0221, 026,  0223, 0224, 0225, 0226, 04,   
  0230, 0231, 0232, 0233, 024,  025,  0236, 032,  
  040,  0240, 0241, 0242, 0243, 0244, 0245, 0246, 
  0247, 0250, 0133, 056,  074,  050,  053,  041,  
  046,  0251, 0252, 0253, 0254, 0255, 0256, 0257, 
  0260, 0261, 0135, 044,  052,  051,  073,  0136, 
  055,  057,  0262, 0263, 0264, 0265, 0266, 0267, 
  0270, 0271, 0174, 054,  045,  0137, 076,  077,  
  0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301, 
  0302, 0140, 072,  043,  0100, 047,  075,  042,  
  0303, 0141, 0142, 0143, 0144, 0145, 0146, 0147, 
  0150, 0151, 0304, 0305, 0306, 0307, 0310, 0311, 
  0312, 0152, 0153, 0154, 0155, 0156, 0157, 0160, 
  0161, 0162, 0313, 0314, 0315, 0316, 0317, 0320, 
  0321, 0176, 0163, 0164, 0165, 0166, 0167, 0170, 
  0171, 0172, 0322, 0323, 0324, 0325, 0326, 0327, 
  0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, 
  0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347, 
  0173, 0101, 0102, 0103, 0104, 0105, 0106, 0107, 
  0110, 0111, 0350, 0351, 0352, 0353, 0354, 0355, 
  0175, 0112, 0113, 0114, 0115, 0116, 0117, 0120, 
  0121, 0122, 0356, 0357, 0360, 0361, 0362, 0363, 
  0134, 0237, 0123, 0124, 0125, 0126, 0127, 0130, 
  0131, 0132, 0364, 0365, 0366, 0367, 0370, 0371, 
  060,  061,  062,  063,  064,  065,  066,  067,  
  070,  071,  0372, 0373, 0374, 0375, 0376, 0377, 
};

main (argc, argv)
     int argc;
     char **argv;
{
  int i;

  /* initialize translation table to identity translation */
  for (i = 0; i < 256; i++)
    trans_table[i] = (char) i;

  /* decode arguments and fill in info structure */
  scanargs (argc, argv);

  if (info.i_if != NULL)
    {
      if ((info.i_ifd = open (info.i_if, 0)) < 0)
	fatal (info.i_if, 1);
      if (info.i_of != NULL)
	if ((info.i_ofd = creat (info.i_of, 0666)) < 0)
	  fatal (info.i_of, 1);
    }
  if (info.i_convert & C_DEBUG)
    {
      printf ("Info: if=%s, of=%s, ibs=%d, obs=%d, cbs=%d, skip=%d,\n",
	      info.i_if, info.i_of, info.i_ibs, info.i_obs, info.i_cbs,
	      info.i_skip);
      printf ("files=%d, seek=%d, count=%d conv=0%o\n",
	      info.i_files, info.i_seek, info.i_count, info.i_convert);
    }
  copy ();
  quit (0);
}

copy ()
{
  register int i, oc = 0;
  int spaces = 0, col = 0;
  char *ibuf, *obuf, *xmalloc ();
  int nread, nwritten;

  ibuf = xmalloc (info.i_ibs);
  if (info.i_ibs != info.i_obs || (info.i_convert & C_HARDWAY))
    obuf = xmalloc (info.i_obs);
  else
    obuf = ibuf;
  
  for (i = 0; i < info.i_skip; i++)
    if (read (info.i_ifd, ibuf, info.i_ibs) < 0)
      fatal (info.i_if, 1);
  
  if (lseek (info.i_ofd, info.i_seek * info.i_obs, 0) < 0)
    fatal (info.i_of, 1);
  
  while ((nread = read (info.i_ifd, ibuf, info.i_ibs)) != 0)
    {
      if (info.i_count >= 0 && r_partial+r_full > info.i_count)
	return;

      if (nread < 0)
	{
	  perror (info.i_if);
	  if (info.i_convert & C_NOERROR)
	    continue;
	  else
	    quit (2);
	}

      if (nread < info.i_ibs)
	{
	  r_partial++;
	  if (info.i_convert & C_SYNC)
	    while (nread < info.i_ibs)
	      ibuf[nread++] = '\0';
	}
      else
	r_full++;

      /* if blocksizes are the same and no conversion, just flush */
      if (ibuf == obuf)
	{
	  if ((nwritten = write (info.i_ofd, obuf, nread)) != nread)
	    {
	      perror (info.i_of);
	      w_partial++;
	      if (!(info.i_convert & C_NOERROR))
		quit (1);
	    }
	  else if (nread == info.i_ibs)
	    w_full++;
	  else
	    w_partial++;
	  continue;
	}
      /*
       * copy input to output, doing conversion and flushing output
       * as necessary.
       */
      for (i = 0; i < nread; i++)
	{
	  register int c = trans_table[ibuf[i] & 0377] & 0377;

	  if (info.i_convert & C_BLOCK)
	    {
	      if (spaces-- > 0)
		{
		  i--;			/* push back char, get it next time */
		  c = trans_table[' '];
		}
	      else if (c == '\n')
		{
		  spaces = max (0, info.i_cbs - col);
		  col = 0;
		  continue;
		}
	      else if (col++ >= info.i_cbs)
		{
		  r_truncate++;
		  continue;
		}
	    }
	  else if (info.i_convert & C_UNBLOCK)
	    {
	      if (col++ >= info.i_cbs)
		{
		  col = spaces = 0;
		  c = trans_table['\n'];
		  i--;			/* get the real c next time */
		}
	      else if (c == ' ')
		spaces++;
	      else if (spaces > 0)
		{
		  i--;
		  spaces--;
		  c = trans_table[' '];
		}
	    }

	  if (info.i_convert & C_SWAB)
	    obuf[oc++ ^ 1] = c;
	  else
	    obuf[oc++] = c;
	  if (oc >= info.i_obs)
	    {
	      if ((nwritten = write (info.i_ofd, obuf, info.i_obs)) != info.i_obs)
		{
		  perror (info.i_of);
		  w_partial++;
		  /* maybe it would be better to try to write out the rest of the
		     block next time instead of throwing it away */
		  if (!(info.i_convert & C_NOERROR))
		    quit (1);
		}
	      else
		w_full++;
	      oc = 0;
	    }
	}
    }

  /* flush last block */
  if (oc > 0)
    {
      /* first, fix earlier screw if swapping bytes and n is odd */
       if ((info.i_convert & C_SWAB) && (oc & 1))
	 obuf[oc-1] = obuf[oc];

      if ((nwritten = write (info.i_ofd, obuf, oc)) != oc)
	perror (info.i_of);
      if (nwritten > 0)
	w_partial++;
    }
}

scanargs (argc, argv)
     int argc;
     char **argv;
{
  int i, n;

  for (i = 1; i < argc; i++)
    {
      char *name, *val, *index ();
      name = argv[i];
      if ((val = index (name, '=')) == NULL)
	{
	  printf ("bad arg: %s\n", name);
	  return -1;
	}
      *val++ = '\0';
    
      if (equal (name, "if"))
	info.i_if = val;
      else if (equal (name, "of"))
	info.i_of = val;
      else if (equal (name, "conv"))
	parse_conversion (val);
      else
	{
	  n = parse_integer (val);
      
	  if (equal (name, "ibs"))
	    info.i_ibs = n;
	  else if (equal (name, "obs"))
	    info.i_obs = n;
	  else if (equal (name, "bs"))
	    info.i_obs = info.i_ibs = n;
	  else if (equal (name, "cbs"))
	    info.i_cbs = n;
	  else if (equal (name, "skip"))
	    info.i_skip = n;
	  else if (equal (name, "seek"))
	    info.i_seek = n;
	  else if (equal (name, "count"))
	    info.i_count = n;
	  else
	    {
	      printf ("bad arg: %s=%s\n", name, val);
	      exit (1);
	    }
	}
    }
}

parse_integer (str)
     char *str;
{
  int n = 0;
  char *p = str;

  while (isdigit (*p))
    {
      n = n * 10 + *p - '0';
      p++;
    }
 loop:
  switch (*p++)
    {
    case '\0':
      return n;
    case 'b':
      n *= 512;
      goto loop;
    case 'k':
      n *= 1024;
      goto loop;
    case 'w':
      n *= 2;
      goto loop;
    case 'x':
      n *= parse_integer (p);
      break;
    default:
      return 0;
    }
  return n;
}

parse_conversion (str)
     char *str;
{
  char *new;
  int i, j;
  char *new_trans;

  do {
    if ((new = index (str, ',')) != NULL)
      *new++ = '\0';
    for (i = 0; conversions[i].convname != NULL; i++)
      if (equal (conversions[i].convname, str))
	{
	  info.i_convert |= conversions[i].conversion;
	  break;
	}
    if (conversions[i].convname == NULL)
      {
	printf ("%s: bad conversion.\n", str);
	exit (1);
      }
#define MX(a) (bit_count (info.i_convert & (a)))
    if ((MX (C_ASCII | C_EBCDIC | C_IBM) > 1)
	|| (MX (C_BLOCK | C_UNBLOCK) > 1)
	|| (MX (C_LCASE | C_UCASE) > 1)
	|| (MX (C_UNBLOCK | C_SYNC) > 1))
      {
	printf ("Only one conv in {ascii,ebcdic,ibm}, {lcase,ucase},\n\t%s",
		"{block,unblock}, {unblock,sync}\n");
	exit (1);
      }
#undef MX
    str = new;
  } while (new != NULL);

  /* I don't know if the following restriction is stupid */
  /* but it's convenient */
  if ((info.i_convert & C_SWAB) && (info.i_ibs % 2 != 0 ||
				    info.i_obs % 2 != 0))
    {
      printf ("Ibs and obs must both be even for swab to work.\n");
      exit (1);
    }

  /* fix up translation table */

  /* do upper and lower case if necessary */
  if (info.i_convert & C_UCASE)
    for (j = 'a'; j <= 'z'; j++)
      trans_table[j] = toupper (j);
  else if (info.i_convert & C_LCASE)
    for (j = 'A'; j <= 'Z'; j++)
      trans_table[j] = tolower (j);

  /* now find and apply char set translation */
  if (info.i_convert & C_ASCII)
    new_trans = ebcdic_to_ascii;
  else if (info.i_convert & C_EBCDIC)
    new_trans = ascii_to_ebcdic;
  else if (info.i_convert & C_IBM)
    new_trans = ascii_to_ibm;
  else
    return;

  for (i = 0; i < 256; i++)
    trans_table[i] = new_trans[trans_table[i]];
}

/* return number of 1 bits in i. */

bit_count (i)
     register int i;
{
  register int n;

  for (n = 0; i != 0; i = (i >> 1) & ~(1<<31))
    n += i & 1;
  return n;
}

/* print fatal error for file, then die */
fatal (filename, code)
     char *filename;
     int code;
{
  perror (filename);
  quit (code);
}

/* print statistics and exit */
quit (code)
{
  fprintf (stderr, "%d+%d records in\n", r_full, r_partial);
  fprintf (stderr, "%d+%d records out\n", w_full, w_partial);
  if (r_truncate > 0)
    fprintf (stderr, "%d truncated records\n", r_truncate);

  exit (code);
}

/* malloc or die */
char *
xmalloc (n)
     int n;
{
  char *p, *malloc ();
  if ((p = malloc (n)) == NULL)
    {
      printf ("Malloc: not enough memory\n");
      exit (3);
    }
}

